#include <Arduino.h>
#include <WiFi.h>
#include <Ticker.h>
#include <EEPROM.h>
#include "SPIFFS.h"
#include <httpUpdate.h>
#include <BLEDevice.h>

int R1 = 360000;   // 360000欧姆
int R2 = 5000;     // 5000欧姆
int R3 = 180000;   // 180000欧姆
int R4 = 10000;    // 10000欧姆
float Ux = 14200;  // 定义蓄电池变量电压
float Uxm = 14200; // 设置蓄电池满电电压
int Ixm = 10000;   // 设置蓄电池满电电流
int Uxq = 1000;    // 设置蓄电池启动电压
float Uxf = 13800; // 设置蓄电池浮充电压
int Uxc = 13000;   // 设置蓄电池解除浮充电压
int Utq = 8000;    // 初始化太阳能启动电压
float Utg = 15000; // 初始化太阳能工作电压
float It = 30000;  // 设置充电电流
int T = 50;        // 设置温度保护
int t = 100;       // 延迟时间/ms
int s = 100;       // 扰动弧度单位/mv

/**
 * 结构体，用于存储配网信息
 */
struct config_type
{
  char stassid[32];
  char stapsw[64];
  uint8_t reboot;
  int data;
  int Uxm; // 设置蓄电池满电电压
  int Uxf; // 设置蓄电池浮充电压
  int Ixm; // 设置蓄电池满电电流
  int It;  // 设置充电电流
  int T;   // 设置温度保护
};
config_type config;
uint8_t *p = (uint8_t *)(&config);
tm timeinfo;

void loadConfig(); // 读取储存数据
void saveConfig(); // 保存数据

int freq = 20000;   // 设置PWM的频率
int ledChannel = 0; // 设置PWM的通道
int resolution = 8; // 设置PWM 占空比的分辨率 最大占空比为 2^8 = 256
int PWM_PIN = 15;   // PWM 信号输出引脚
int SD_PIN = 2;     // PWM 信号使能引脚
int tyn_PIN = 16;   // 太阳能使能引脚
int Duty = 100;     // 定义 PWM 占空比的初始值
int spk_PIN = 12;   // 蜂鸣器引脚

float ble_U, ble_U1, ble_U2, ble_U3, ble_U4, ble_U1234, ble_I, ble_T1, ble_T2, ble_IN, ble_OUT, ble_dl; // 读取的电池组参数
float tyn_U, tyn_I, tyn_P, xdc_U, xdc_I, Q, kWh, MOS_T, zuoTiankWh;                                     // 读取的太阳能参数

boolean ble = true;        // 蓝牙使能
boolean in = false;        // 输出使能
boolean out = true;        // 充电开关
boolean mppt_kg = true;    // 智能开关
boolean mppt_cdzt = false; // 蓄电池充电状态浮充
boolean update = false;    //

const char *ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
Ticker timerTick; // 创建 Ticker 对象

void yourFunction();
void operateOnDataInFile(const char *filename, String targetChar, String action, float &newData);
bool processFile(String targetValue, char parameterData[]);

#include <esp32_Bfy.h>
#include <esp32_Adc.h>
#include <esp32_Mppt.h>
#include <esp32_Udp.h>
#include <esp32_ble.h>
#include <esp32_1602.h>

Ticker delayTimer;
void delayRestart(float t)
{
  delayTimer.attach(t, []()
                    { ESP.restart(); });
}

/**
 * 恢复出厂设置，清除存储的wifi信息
 */
void restoreFactory()
{
  Serial.println("Restore Factory....... ");
  config.Uxm = 14500; // 设置蓄电池满电电压
  config.Uxf = 13800; // 设置蓄电池浮充电压
  config.Ixm = 10000; // 设置蓄电池满电电流
  config.It = 30000;  // 设置充电电流
  config.T = 50;      // 设置温度保护
  config.reboot = 0;
  saveConfig();
  delayRestart(1);
  while (1)
  {
    delay(100);
  }
}
/**
 * 存储配网信息
 */
void saveConfig()
{
  EEPROM.begin(256);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}

/**
 * 加载存储的信息，并检查是否进行了反复5次重启恢复出厂信息
 */
void loadConfig()
{
  uint8_t mac[6];
  WiFi.macAddress(mac);
  EEPROM.begin(256);
  for (int i = 0; i < sizeof(config); i++)
  {
    *(p + i) = EEPROM.read(i);
  }
  config.reboot = config.reboot + 1;
  if (config.reboot >= 10)
  {
    restoreFactory();
  }

  EEPROM.begin(256);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
  Serial.printf("config.reboot:%d", config.reboot);
  if (!SPIFFS.begin()) // 启动闪存文件系统
  {
    Serial.println("begin");
    SPIFFS.format();
  }
  else // 读取SPIFFS分区
  {
    Serial.println("");
    Serial.println("SPIFFS OK!");
    // 检查文件是否存在
    if (!SPIFFS.exists("/data.txt") && !SPIFFS.exists("/charts.txt"))
    {
      Serial.println("新建文件");
      File zws1 = SPIFFS.open("/data.txt", "w");
      File zws2 = SPIFFS.open("/charts.txt", "w");
    }
    // uint32_t totalBytes = SPIFFS.totalBytes();
    // uint32_t usedBytes = SPIFFS.usedBytes();
    // Serial.print("总空间: ");
    // Serial.print(totalBytes);
    // Serial.println(" 字节");
    // Serial.print("已使用空间: ");
    // Serial.print(usedBytes);
    // Serial.println(" 字节");
    // Serial.print("剩余空间: ");
    // Serial.print(totalBytes - usedBytes);
    // Serial.println(" 字节");
    File zws1 = SPIFFS.open("/data.txt", "r");
    // Serial.print(zws1.readString());
    zws1.close();
    File zws2 = SPIFFS.open("/charts.txt", "r");
    // Serial.println(zws2.readString());
    zws2.close();
    operateOnDataInFile("/data.txt", "Q", "R", Q);
    operateOnDataInFile("/data.txt", "kWh", "R", kWh);
    operateOnDataInFile("/data.txt", "zuoTiankWh", "R", zuoTiankWh);
  }
  Uxm = config.Uxm; // 设置蓄电池满电电压
  Uxf = config.Uxf; // 设置蓄电池浮充电压
  Ixm = config.Ixm; // 设置蓄电池满电电流
  It = config.It;   // 设置充电电流
  T = config.T;     // 设置温度保护
  Serial.print("充电电压:");
  Serial.print(Uxm / 1000);
  Serial.println("V");
  Serial.print("浮充电压:");
  Serial.print(Uxf / 1000);
  Serial.println("V");
  Serial.print("充电电流:");
  Serial.print(It / 1000);
  Serial.println("A");
  Serial.print("温度保护:");
  Serial.print(T);
  Serial.println("°C");
  EEPROM.begin(256);
  config.reboot = 0;
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}
/*
 *在文件中查找指定数据并读取数据
 */
bool processFile(String targetValue, char parameterData[])
{

  File file = SPIFFS.open("/charts.txt", "r");
  if (!file)
  {
    Serial.println("Failed to open file");
    return false;
  }
  // 读取月份数据
  size_t i;
  String content;
  while (file.available())
  {
    content = file.readStringUntil('\n');
    if (content.startsWith(targetValue))
    {
      for (i = 0; i < content.length() && i < 126; i++)
      {
        parameterData[i] = content[i];
      }
      parameterData[i++] = '\0'; // 确保字符串结束
      return true;
    }
  }

  file.close();
  return false;
}
/*
 *在文件中查找指定数据并修改后面的数据 W修改 , R读取
 */
void operateOnDataInFile(const char *filename, String targetChar, String action, float &newData)
{ // 使用 int 引用
  File file = SPIFFS.open(filename, "r+");

  if (!file)
  {
    Serial.println("Failed to open file");
    File file = SPIFFS.open(filename, "w"); // 如果文件不存在，创建新文件
  }
  if (newData > 9999.0000000) // 如果写入的数据超出存储空间
  {
    Serial.println("超出数据范围");
    return;
  }
  bool found = false; // 用于标记是否找到匹配行
  String content;
  while (file.available())
  {
    content = file.readStringUntil('\n');
    if (content.startsWith(targetChar))
    { // 使用 startsWith 更简洁地判断是否匹配
      found = true;
      if (action == "R")
      {
        Serial.println("读取数据");
        String data = content.substring(targetChar.length()); // 提取目标字符后面的数据
        newData = data.toFloat();                             // 将字符串转换为 float 并传出
                                                              // Serial.print(newData);
                                                              // Serial.print("\n");
      }
      else if (action == "W")
      {
        Serial.println("更新数据");
        file.seek(file.position() - (content.length() - targetChar.length()));
        while (file.peek() != '\n')
        {
          file.print(" ");
        }
        file.seek(file.position() - (content.length() - targetChar.length() - 1));
        char buffer[20];
        sprintf(buffer, "%.7f", newData);
        file.print(buffer);
      }
      break; // 找到匹配后就不再继续查找
    }
  }

  if (!found)
  { // 如果未找到匹配且是写入操作
    Serial.println("写入数据");
    file.print(targetChar);
    for (size_t i = 0; i < 13; i++)
    {
      file.print(" ");
    }
    file.print("\n");
    file.seek(file.position() - 12);
    char buffer[20];
    sprintf(buffer, "%.7f", newData);
    file.print(buffer);
  }
  // file.seek(0);
  // Serial.println(file.readString());
  file.close();
}
/*
 *计算今天还要多少秒并返回值
 */
long calculateTimeUntilMidnight(struct tm timeNow)
{
  long hoursUntilMidnight = 3600 * timeNow.tm_hour;
  long minutesUntilMidnight = 60 * timeNow.tm_min;
  long secondsUntilMidnight = timeNow.tm_sec;
  long totalSecondsUntilMidnight = 86400 - hoursUntilMidnight - minutesUntilMidnight - secondsUntilMidnight;
  Serial.printf("当前时间");
  Serial.print(asctime(&timeNow));
  Serial.printf("距离00:00还有%d秒", totalSecondsUntilMidnight);
  Serial.println("");
  return totalSecondsUntilMidnight; // 返回秒数
}
/*
 *定时中断函数
 */
void yourFunction()
{
  Serial.println("定时中断函数运行");
  if (!getLocalTime(&timeinfo))
  {
    Serial.println("时间未校准");
    sendtoTCPServer("cmd=2&uid=" + UID +
                    "&topic=" + TOPIC +
                    "/up&msg=时间未校准\r\n"); // 推送消息
    return;
  }
  else
  {
    // struct tm {
    // int tm_sec; // 秒，取值0~59；
    // int tm_min; // 分，取值0~59；
    // int tm_hour; // 时，取值0~23；
    // int tm_mday; // 月中的日期，取值1~31；
    // int tm_mon; // 月，取值0~11；
    // int tm_year; // 年，其值等于实际年份减去1900；
    // int tm_wday; // 星期，取值0~6，0为周日，1为周一，依此类推；
    // int tm_yday; // 年中的日期，取值0~365，0代表1月1日，1代表1月2日，依此类推；
    // int tm_isdst; // 夏令时标识符，实行夏令时的时候，tm_isdst为正；不实行夏令时的进候，tm_isdst为0；不了解情况时，tm_isdst()为负
    // };

    // 打开文件
    File charts = SPIFFS.open("/charts.txt", "r+"); // 储存每天用电数文件
    // 读取当月月份
    char mon[5];
    sprintf(mon, "%02d", timeinfo.tm_mon + 1);
    int mday = 0;
    int seek = 0;
    String content = "";
    bool yiyang = false;
    while (charts.available())
    {
      seek = charts.position();
      content = charts.readStringUntil('\n');
      // Serial.println(content);
      // 判断当月有没有数据记录
      if (content.startsWith(mon))
      {
        mday = content.length() / 4;
        yiyang = true;
        // charts.seek(seek-2);
        // charts.print("0");
        // charts.print("\n");
        // charts.print(mon);
        break;
      }
    }
    // 判断当月如果没有数据换行重新记录
    if (!yiyang)
    {
      Serial.println("没找到");
      if (charts.size() > 0)
      {
        charts.print("\n");
      }
      charts.print(mon);
    }
    // 判断数据量和日期是否一样
    if (mday < timeinfo.tm_mday)
    {
      for (size_t i = 1; i <= timeinfo.tm_mday - mday; i++)
      {
        charts.printf("0000");
      }
      zuoTiankWh = kWh;
      operateOnDataInFile("/data.txt", "zuoTiankWh", "W", zuoTiankWh);
    }
    // 更新当天数据
    else
    {
      char data[5];
      charts.seek(seek + 2 + ((timeinfo.tm_mday - 1) * 4));
      int i = (kWh - zuoTiankWh) * 100;
      sprintf(data, "%04d", i);
      charts.print(data);
    }

    // 关闭文件
    charts.close();
  }
}

void diangshiqi()
{
  if (!timerTick.active() && WiFi.status() == WL_CONNECTED && getLocalTime(&timeinfo))
  {
    Serial.println("定时成功");
    long timeUntilMidnight1 = calculateTimeUntilMidnight(timeinfo);
    timerTick.once(timeUntilMidnight1, yourFunction); // 重新设置下一天的触发
  }
}

void setup()
{
  Serial.begin(115200);     // 初始化串口通信速率
  pinMode(23, OUTPUT);      // 设置引脚为 输出模式
  pinMode(spk_PIN, OUTPUT); // 设置引脚为 输出模式

  digitalWrite(spk_PIN, HIGH); // 指示灯引脚
  delay(500);
  digitalWrite(spk_PIN, LOW); // 指示灯引脚

  digitalWrite(tyn_PIN, LOW);
  digitalWrite(SD_PIN, LOW);

  loadConfig();   // 加载存储的信息
  chushihuaPWM(); // 初始化PWM
  chushihuaADC(); // 初始化ADC
  chushuhuable(); // 初始化蓝牙
  kanmenggou();   // 开启看门狗
  setup1602();
  startSTA();     // 网络连接
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}

void loop()
{
  qidongBFY();
  qidongADC();
  qidongMPPT();
  if (millis() - preHeartTick1 >= 1000 && WiFi.status() == WL_CONNECTED && TCPclient.connected())
  {
    // qidongble();
    qidongUDP();
    qidong1602();
    diangshiqi();
  }
}